const commondUtils = require("./commondUtils.js");
const storageUtil = require("./storageUtils.js");
const wsTools = require('./websocketSingleClient.js');

const clockInHandle = {};


const defaultSettingData = {
    "screenPassword": "123456",
    "m3Username": "test",
    "m3Password": "123456",
    "isEmailNotify": true, // 是否邮件通知
    "emailAddr": "1234567@qq.com",
    "dayRangeList": [1, 2, 3, 4, 5], // 签到范围-星期几
    "hourRangeList": [8], // 签到范围-小时
    "minuteRangeList": [25, 26, 27, 28, 29, 30, 31, 32], // 签到范围-分钟
    "intervalTime": 60000, // 定时任务间隔时间 60s
    "ignoreDateList": [], // 忽略的日期
    "specialDateList": [], // 特殊日期（非工作日）
    "latestPoint": "08:35", // 最晚签到时间点
    "latestPointDate": null,
    "intervalModel": "local,websocket", // local：本地， websocket：服务端
    "websocketUrl": "", // websocket地址
    "reconnectMaxNum": 10, // websocket最大重连次数，小于1代表无限重连，总时长 = ((x-6)*300_000 + 310_000) / 60_000 m
}
// 加载配置
const settingData = storageUtil.loadSettingData(defaultSettingData);
settingData.latestPointDate = parserLatestPoint(settingData.latestPoint);

clockInHandle.getSettingData = () => {
    return settingData;
}

// region 服务端模式 websocket

let curSignRecordId = null;

const LOG_LEVEL_INFO = "info";
const LOG_LEVEL_WARN = "warn";
const LOG_LEVEL_ERROR = "error";

/**
 * 打印日志，回传签到过程消息
 * @param level
 * @param msg
 */
function printLog(level, msg) {
    if (commondUtils.isBlank(level) || commondUtils.isBlank(msg)) {
        console.error("日志入参[level|msg]为空！")
        return;
    }
    // 替换消息中的占位符 {}
    let formatMsg = msg;
    for (let i = 2; i < arguments.length; i++) {
        formatMsg = formatMsg.replace("{}", arguments[i]);
    }
    let nowTimeStr = commondUtils.getCurrentTimeStr();
    // 构建日志内容 `[${nowStr}] [${level.toUpperCase()}] ${formatMsg}`;
    let logContent = "[" + nowTimeStr + "] " + "[" + level.toUpperCase() + "] " + formatMsg;
    if (level == LOG_LEVEL_ERROR) {
        const e = arguments[arguments.length - 1];
        if (e instanceof Error) {
            logContent += " \n " + JSON.stringify(e);
        }
    }
    console[level](logContent);

    if (wsTools.isConnected() && commondUtils.isBlank(curSignRecordId)) {
        return;
    }
    // 回传签到过程消息
    wsTools.sendMsg({
        "type": "sign_process",
        "data": {
            "signRecordId": curSignRecordId,
            "date": nowTimeStr,
            "msg": logContent,
        }
    });
}

/**
 * 连接服务端
 */
clockInHandle.connectServer = () => {
    if (commondUtils.isBlank(settingData.websocketUrl)) {
        alert("服务端地址为空！请修改本地配置文件");
        return;
    }
    const startConnectTime = Date.now();
    threads.start(function () {
        wsTools.connect(settingData.websocketUrl, {
            reconnectMaxNum: settingData.reconnectMaxNum,
            onOpen: (response) => {
                toast("连接服务端成功！" + ((Date.now() - startConnectTime) / 1000) + "s");
            },
            onMessage: (msg) => {
                const dataKeys = ["screenPassword", "m3Username", "m3Password", "signRecordId", "latestPoint"]
                let data;
                try {
                    const msgObj = JSON.parse(msg);
                    if (msgObj.type !== 'sign') {
                        return;
                    }
                    data = JSON.parse(msgObj.data);
                    if (commondUtils.isEmpty(data)) {
                        printLog(LOG_LEVEL_ERROR, "收到的签到消息data数据为空!");
                        return;
                    }
                    for (let key of dataKeys) {
                        if (commondUtils.isBlank(data[key])) {
                            printLog(LOG_LEVEL_ERROR, "收到的签到消息data.{}为空!", key);
                            return;
                        }
                    }
                } catch (e) {
                    printLog(LOG_LEVEL_ERROR, "解析签到消息异常", e);
                    return;
                }

                // 正在签到中
                if (curSignRecordId != null) {
                    printLog(LOG_LEVEL_WARN, "客户端正在签到中，请等待签到完成后重试. curSignRecordId：{}", curSignRecordId);
                    sleep(500);
                    wsTools.sendMsg({
                        "type": "sign",
                        "data": {
                            "signRecordId": data.signRecordId,
                            "statusCode": 0,
                            "signTime": null
                        }
                    });
                    return;
                }

                // 本地任务正在签到中
                if (execTaskId != null) {
                    printLog(LOG_LEVEL_WARN, "客户端本地任务正在签到中");
                    sleep(500);
                    wsTools.sendMsg({
                        "type": "sign",
                        "data": {
                            "signRecordId": data.signRecordId,
                            "statusCode": 0,
                            "signTime": null
                        }
                    });
                    return;
                }


                settingData.latestPoint = data.latestPoint;
                settingData.latestPointDate = parserLatestPoint(settingData.latestPoint);
                curSignRecordId = data.signRecordId;

                printLog(LOG_LEVEL_INFO, "开始执行签到任务");
                // 唤醒屏幕并解锁
                unlockScreen(data.screenPassword);
                sleep(1000);
                try {
                    const signDate = singInMain(data.m3Username, data.m3Password, false);
                    wsTools.sendMsg({
                        "type": "sign",
                        "data": {
                            "signRecordId": data.signRecordId,
                            "statusCode": signDate == null ? 0 : 1,
                            "signTime": signDate
                        }
                    });
                } catch (e) {
                    printLog(LOG_LEVEL_ERROR, "签到任务执行异常", e);
                }
                curSignRecordId = null;
                lockScreen();
            },
        });
    });
}

/**
 * 关闭连接
 * @returns {boolean}
 */
clockInHandle.closeServer = () => {
    console.info("用户操作关闭服务端连接");
    wsTools.closeWs();
}

/**
 * 是否已连接服务端
 * @returns {boolean}
 */
clockInHandle.isConnectServer = () => {
    return wsTools.isConnected();
}

// endregion

// region 本地定时任务模式 local

const LAST_EXEC_TIME = 'lastExecTime';
// 执行任务
let execTaskId;
// 定时任务
let intervalId;

/**
 * 是否开启定时任务
 * @returns {boolean}
 */
clockInHandle.isOpenInterval = () => {
    return intervalId != null;
}

/**
 * 启动定时任务
 */
clockInHandle.runClockInTask = () => {
    if (commondUtils.isEmpty(settingData)) {
        console.error("启动定时任务失败，配置为空");
        return;
    }
    if (intervalId != null) {
        clearInterval(intervalId);
        intervalId = null;
        sleep(1000);
    }
    intervalId = setInterval(() => {
        if (execTaskId != null) {
            // 正在执行
            return;
        }
        execTask(buildPlanExecTime(new Date()), false);
    }, settingData.intervalTime);
    console.info("启动定时任务成功");
}

/**
 * 停止定时任务
 */
clockInHandle.stopClockInTask = () => {
    if (intervalId == null) {
        return;
    }
    clearInterval(intervalId);
    intervalId = null;
    console.info("定时任务已停止");
}

/**
 * 测试定时任务
 */
clockInHandle.testClockInTask = () => {
    if (execTaskId != null) {
        // 正在执行
        return;
    }
    // 锁屏
    lockScreen();
    console.info("测试锁屏任务,15s后执行...");
    // 15S后执行
    const testTime = new Date(new Date().getTime() + 15 * 1000);
    execTask(testTime, true);
}

/**
 * 立即签到
 */
clockInHandle.execSingIn = () => {
    singInMain(settingData.m3Username, settingData.m3Password, false);
}

function execTask(planExecTime, testFlag) {
    const lastExecTime = testFlag ? null : storageUtil.getLocalData(storageUtil.PUCLIC_STORAGE_NAME, LAST_EXEC_TIME);
    if (planExecTime == null || isSameHour(planExecTime, lastExecTime)) {
        // 不符合时间 || 当天该小时已执行
        return;
    }

    // 正在进行客户端签到任务
    if (curSignRecordId != null) {
        console.log("正在进行客户端签到任务.", curSignRecordId);
        return;
    }

    console.info("计划执行时间：%s", commondUtils.formatDate(planExecTime, true, false));
    let waitTime = 0;
    const now = new Date();
    if (planExecTime > now) {
        waitTime = Math.ceil(planExecTime.getTime() - now.getTime());
        console.info("延迟%s秒后执行...", waitTime / 1000);
    }

    execTaskId = threads.start(function () {
        sleep(waitTime);
        console.info("[%s] 开始执行签到任务", commondUtils.formatDate(new Date(), true, false));
        // 唤醒屏幕并解锁
        unlockScreen(settingData.screenPassword);
        sleep(1000);
        try {
            const signDate = singInMain(settingData.m3Username, settingData.m3Password, false);
            if (settingData.isEmailNotify && (signDate != null || testFlag)) {
                sendSignNoticeEmail(settingData.emailAddr, signDate);
            }
        } catch (e) {
            console.error("任务执行异常!", e);
        }
        execTaskId = null;
        lockScreen();
    });
}

/**
 * 判断时间是否相同（精确到小时）
 * @param date1
 * @param date2
 * @returns {boolean}
 */
function isSameHour(date1, date2) {
    // if (!date1 && !date2) {
    //     return true;
    // }
    if (!date1 || !date2) {
        return false;
    }
    date1 = new Date(date1);
    date2 = new Date(date2);
    return date1.getFullYear() == date2.getFullYear()
        && date1.getMonth() == date2.getMonth()
        && date1.getDate() == date2.getDate()
        && date1.getHours() == date2.getHours();
}


clockInHandle.isInExecTimeRange = isInExecTimeRange;

/**
 * 是否在执行时间范围内
 * @param {Date} date
 */
function isInExecTimeRange(date) {
    // 将当前日期格式化为 'YYYY-MM-DD' 字符串
    const currentDateStr = date.getFullYear()
        + '-' + String(date.getMonth() + 1).padStart(2, '0')
        + '-' + String(date.getDate()).padStart(2, '0');

    // 忽略的日期
    if (settingData.ignoreDateList.includes(currentDateStr)) {
        return false;
    }

    // 特殊日期：检查小时和分钟是否符合，普通日期：检查星期、小时、分钟
    return (settingData.specialDateList.includes(currentDateStr) || settingData.dayRangeList.includes(date.getDay()))
        && settingData.hourRangeList.includes(date.getHours())
        && settingData.minuteRangeList.includes(date.getMinutes());
}

/**
 * 构建任务计划执行时间
 * @param date
 * @returns {null|Date}
 */
function buildPlanExecTime(date) {
    if (date == null) {
        date = new Date();
    }

    // 是否在执行时间范围内
    if (!isInExecTimeRange(date)) {
        return null;
    }

    const minute = date.getMinutes();
    const second = date.getSeconds();
    // 根据随机索引获取列表中的元素
    const randomMinute = Math.max(settingData.minuteRangeList[commondUtils.getRandomInt(0, settingData.minuteRangeList.length - 1)], minute);
    const randomSecond = Math.max(commondUtils.getRandomInt(0, 59), second);
    const planExecTime = new Date();
    planExecTime.setMinutes(randomMinute, randomSecond);
    return planExecTime;
}

function sendSignNoticeEmail(emailAddr, signDate) {
    sendEmail([emailAddr], "m3-click 签到通知",
        "今日份签到成功，签到时间：" + commondUtils.formatDate(signDate, true, false));
}

// endregion

// region 签到业务代码

const m3PgName = "com.seeyon.cmp";

function singInMain(m3Username, m3Password, testFlag) {
    let flag = launch(m3PgName) || launchApp('移动办公M3');
    if (!flag) {
        printLog(LOG_LEVEL_ERROR, "打开[移动办公M3]APP失败");
        return null;
    }

    sleep(3000);

    // 等待APP加载
    let waitTimeout = 10000;
    printLog(LOG_LEVEL_INFO, "等待打开[移动办公M3]APP...");
    let operationList;
    let starTime = new Date().getTime();
    while (true) {
        // 判断当前所处界面，决定执行步骤
        operationList = judgeCurrentM3Page();
        let waitTime = new Date().getTime() - starTime;
        if (operationList.length > 0) {
            break;
        }
        if (waitTime > waitTimeout) {
            printLog(LOG_LEVEL_ERROR, "当前所处界面未知，判断超时...{}s", waitTimeout / 1000);
            break;
        }
        sleep(1000);
    }
    if (operationList.length < 1) {
        killApp(m3PgName);
        return null;
    }

    sleep(1000);

    if (operationList.includes(1)) {
        flag = login(m3Username, m3Password);
        if (!flag) {
            printLog(LOG_LEVEL_ERROR, "M3移动办公登录失败");
            return null;
        }
        printLog(LOG_LEVEL_INFO, "M3移动办公登录成功");
    }
    if (operationList.includes(2)) {
        flag = switchToWorkTab();
        if (!flag) {
            printLog(LOG_LEVEL_ERROR, "切换至工作门户失败");
            return null;
        }
        printLog(LOG_LEVEL_INFO, "切换至工作门户");
    }
    if (operationList.includes(3)) {
        flag = intoHrSystem();
        if (!flag) {
            printLog(LOG_LEVEL_ERROR, "进入HR系统失败");
            return null;
        }
        printLog(LOG_LEVEL_INFO, "进入HR系统");
    }
    if (operationList.includes(4)) {
        flag = retryTask(intoSignPanel, 5, "进入签到界面",
            () => {
                if (!textContains("去签到").exists()) {
                    back();
                    intoHrSystem();
                }
            });
        if (!flag) {
            printLog(LOG_LEVEL_ERROR, "进入签到界面失败");
            return null;
        }
        printLog(LOG_LEVEL_INFO, "进入签到界面");
    }

    const signDate = reliableSignIn(testFlag);

    // 关闭APP
    killApp(m3PgName);

    return signDate;
}

/**
 * 判断当前所处界面
 * @returns {Array} 执行操作集合
 */
function judgeCurrentM3Page() {
    // 登录界面
    if (text("请登录").exists()) {
        printLog(LOG_LEVEL_INFO, "当前在登录界面");
        return [1, 2, 3, 4, 5];
    }
    // 消息界面
    if (idContains(":id/toolbar_title").text("消息").exists()) {
        printLog(LOG_LEVEL_INFO, "当前在消息界面");
        return [2, 3, 4, 5];
    }
    // 工作门户界面
    if (text("工作门户").idContains("topNavPortalName").clickable(false).exists()) {
        printLog(LOG_LEVEL_INFO, "当前在工作门户界面");
        return [3, 4, 5];
    }
    // HR系统界面
    if (textContains("去签到").exists()) {
        printLog(LOG_LEVEL_INFO, "当前在HR系统界面");
        return [4, 5];
    }
    // 签到界面
    if (text("签到定位").exists()) {
        printLog(LOG_LEVEL_INFO, "当前在签到界面");
        return [5];
    }
    return [];
}

/**
 * 重试任务
 * @param taskFun   任务，必须返回 Boolean 值
 * @param reTryNum  重试次数
 * @param logText   重试日志，任务名称
 * @param reIntoFun 重进界面操作
 * @returns {boolean}
 */
function retryTask(taskFun, reTryNum, logText, reIntoFun) {
    if (typeof taskFun !== 'function') {
        printLog(LOG_LEVEL_ERROR, "重试任务为空");
        return false;
    }
    let flag = false;
    let tryNum = 0;
    while (tryNum < reTryNum) {
        flag = taskFun();
        if (flag) {
            break;
        }
        sleep(1000);
        tryNum++;
        printLog(LOG_LEVEL_INFO, "重试{}...{}", logText, tryNum);
        // 重进界面操作
        typeof reIntoFun == 'function' && reIntoFun()
    }
    return flag;
}

function login(username, password) {
    // 输入用户名
    setText(0, username);
    // 输入密码
    setText(1, password);
    // 点击同意协议按钮
    const checkedDom = idContains("ll_checkbox_parent").visibleToUser().findOnce();
    if (checkedDom == null) {
        printLog(LOG_LEVEL_ERROR, "未匹配到控件[同意协议]");
        return false;
    }
    if (!checkedDom.checked()) {
        checkedDom.click();
    }
    // idContains(":id/btn_login").
    return click("登录");
}

function switchToWorkTab() {
    const workTab = text("工作门户").idContains("main_tab_tv_name").visibleToUser().findOne(8000);
    if (workTab == null) {
        printLog(LOG_LEVEL_ERROR, "未匹配到控件[工作门户]");
        return;
    }
    return click(workTab.bounds().centerX(), workTab.bounds().centerY());
}

function intoHrSystem() {
    const hrSysDom = text("HR系统").visibleToUser().findOne(10000);
    if (hrSysDom == null) {
        printLog(LOG_LEVEL_ERROR, "未匹配到控件[HR系统]");
        return false;
    }
    return click(hrSysDom.bounds().centerX(), hrSysDom.bounds().centerY());
}

function intoSignPanel() {
    const intoSignBtn = textContains("去签到").visibleToUser().findOne(10000);
    if (intoSignBtn == null) {
        printLog(LOG_LEVEL_ERROR, "未匹配到控件[去签到]");
        return false;
    }
    return click(intoSignBtn.bounds().centerX(), intoSignBtn.bounds().centerY());
}

function reliableSignIn(testFlag) {
    // 等待定位完成
    printLog(LOG_LEVEL_INFO, "等待定位...");
    waitLocation();
    sleep(2000);

    let signDate = null;
    const reSignNum = 5;
    let signNum = 0;
    while (!textContains("打卡成功").exists() && signNum < reSignNum) {
        signNum++;
        printLog(LOG_LEVEL_INFO, "{}打卡...{}", (signNum > 1 ? "重试" : "开始"), signNum);

        signDate = signIn(testFlag);
        if (signDate != null) {
            break;
        }
        if (signNum < reSignNum) {
            printLog(LOG_LEVEL_INFO, "刷新定位");
            // 刷新定位
            click(1000, 250);
            // 等待定位完成
            waitLocation();
            sleep(1000);
        }
    }
    return signDate;
}

/**
 * 等待定位完成
 */
function waitLocation() {
    const startDate = new Date();
    let dwDom = textContains("正在定位您的位置").findOne(3000);
    while (dwDom != null) {
        printLog(LOG_LEVEL_INFO, dwDom.text());
        sleep(1000);
        dwDom = textContains("正在定位您的位置").findOne(3000);
    }
    const endDate = new Date();
    printLog(LOG_LEVEL_INFO, "定位完成...{}s", (endDate.getTime() - startDate.getTime()) / 1000);
}

/**
 * 签到
 * @param testFlag
 * @returns {null|Date}
 */
function signIn(testFlag) {
    sleep(2000);
    // 签到
    let signBtn = idContains("singbtntime").boundsInside(374, 1417, 706, 1750).visibleToUser().findOne(5000)
        || idContains("btnsignok").boundsInside(374, 1417, 706, 1750).visibleToUser().findOne(5000);
    if (signBtn == null) {
        printLog(LOG_LEVEL_ERROR, "未匹配到控件[签到]");
        return null;
    }

    const signDate = new Date();
    const signTimeStr = commondUtils.formatDate(signDate, true, false);
    if (testFlag) {
        console.info("打卡测试成功: ", signTimeStr);
        sleep(1000);
        return signDate;
    }

    // 禁止打卡时间
    if (settingData.latestPointDate != null 
        && signDate.getHours() == settingData.latestPointDate.getHours()
        && signDate.getMinutes() > settingData.latestPointDate.getMinutes()) {
        printLog(LOG_LEVEL_ERROR, "打卡时间超出规定范围! {}", signTimeStr);
        return null;
    }

    click(signBtn.bounds().centerX(), signBtn.bounds().centerY());
    sleep(1000);

    if (!textContains("打卡成功").exists()) {
        printLog(LOG_LEVEL_ERROR, "打卡失败!");
        return null;
    }
    printLog(LOG_LEVEL_INFO, "今日份签到成功~");
    toast("今日份签到成功~");
    // 存储最后执行时间
    storageUtil.saveLocalData(storageUtil.PUCLIC_STORAGE_NAME, LAST_EXEC_TIME, new Date().getTime())
    return signDate;
}

/**
 * 解析最后签到时间
 * @param {string} latestPoint 
 * @returns {date}
 */
function parserLatestPoint(latestPoint) {
    if (commondUtils.isBlank(latestPoint)) {
        return null;
    }
    try {
        const points = latestPoint.split(":");
        const hour = parseInt(points[0]);
        const min = parseInt(points[1]);
        const date = new Date();
        date.setHours(hour);
        date.setMinutes(min);
        date.setSeconds(0);
        date.setMilliseconds(0);
        return date;
    } catch (e) {
        console.error("解析最晚签到时间异常! latestPoint:{%s}", latestPoint, e);
    }
    return null;
}

// endregion

// region 通用方法

/**
 * 发送邮件
 * @param {string|array<string>} emailAddr 收件人地址
 * @param {string} title 标题
 * @param {string} content 内容
 * @param {string} attachmentPath 附件文件路径
 */
function sendEmail(emailAddr, title, content, attachmentPath) {
    try {
        // home();
        sleep(1000);
        const emailObj = {
            'email': emailAddr,
            'subject': title,
            'text': content,
            'attachment': attachmentPath,
        };
        app.sendEmail(emailObj);

        const emailBtn = text("电子邮件").visibleToUser().findOne(6000);
        if (emailBtn == null) {
            console.error("未匹配到控件[电子邮件]");
            return false;
        }
        sleep(1000);
        if (!click(emailBtn.bounds().centerX(), emailBtn.bounds().centerY())) {
            console.error("[邮件]按钮点击失败");
            return false;
        }

        const sendEmailBtn = id("com.android.email:id/compose_send_btn").visibleToUser().findOne(3000) || text("发送").visibleToUser().findOne(3000);
        if (sendEmailBtn == null) {
            console.error("未匹配到控件[邮件-发送]");
            return false;
        }
        if (!sendEmailBtn.click()) {
            console.error("点击[邮件-发送]按钮失败");
            return false;
        }
        console.info("发送邮件成功", emailObj);
    } catch (e) {
        // 没有安装邮箱应用，则抛出ActivityNotException。
        log(e)
        console.error("发送邮件异常", e);
        return false;
    }
    return true;
}

/**
 * 输入锁屏密码
 * @param password
 */
function inputPwd(password) {
    for (let i = 0; i < password.length; i++) {
        let p = password[i].toString();
        let pDom = text(p).visibleToUser().findOnce() || desc(p).visibleToUser().findOnce();
        if (pDom == null) {
            printLog(LOG_LEVEL_ERROR, "未匹配到密码按键{}", p);
            return;
        }
        click(pDom.bounds().centerX(), pDom.bounds().centerY());
        sleep(100);
    }
}

/**
 * 解锁屏幕
 * @param password
 */
function unlockScreen(password) {
    if (device.isScreenOn()) {
        return;
    }
    printLog(LOG_LEVEL_INFO, "唤醒屏幕");
    device.wakeUp();
    sleep(500);
    swipe(500, 2000, 500, 1000, 210);
    sleep(500);
    if (commondUtils.isBlank(password)) {
        return;
    }
    printLog(LOG_LEVEL_INFO, "输入锁屏密码");
    inputPwd(password);
}

/**
 * 关闭应用
 * @param pgName
 * @param appName
 */
function killApp(pgName, appName) {
    if (commondUtils.isEmpty(pgName)) {
        pgName = app.getPackageName(appName);
    }
    app.openAppSetting(pgName);

    sleep(1500);

    const stopBtn = text("强行停止").visibleToUser().findOne(1000) || text("结束运行").visibleToUser().findOne(1000);
    if (stopBtn == null) {
        printLog(LOG_LEVEL_WARN, "未匹配到控件[强行停止|结束运行]");
        return;
    }

    if (!stopBtn.enabled()) {
        printLog(LOG_LEVEL_INFO, "APP已结束运行");
        home();
        return;
    }

    click(stopBtn.bounds().centerX(), stopBtn.bounds().centerY());
    sleep(1000);

    const confirmBtn = text("确定").visibleToUser().findOne(1000) || text("强行停止").visibleToUser().findOne(1000);
    if (confirmBtn == null) {
        printLog(LOG_LEVEL_WARN, "未匹配到控件[结束运行-确定|强行停止]");
        return;
    }
    confirmBtn.click();
    sleep(500);
    home();
    printLog(LOG_LEVEL_INFO, "已结束运行APP，并回到HOME页");
}

/**
 * 锁屏
 */
function lockScreen() {
    runtime.accessibilityBridge.getService().performGlobalAction(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
}

// endregion

module.exports = clockInHandle;